package top.wangjiu.reactive.rpc.proxy;

import lombok.AllArgsConstructor;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import top.wangjiu.reactive.rpc.client.RpcClient;
import top.wangjiu.reactive.rpc.config.ClientConfiguration;
import top.wangjiu.reactive.rpc.config.ServiceConfig;
import top.wangjiu.reactive.rpc.generater.RequestIdGenerater;
import top.wangjiu.reactive.rpc.loadbalance.LoadBalance;
import top.wangjiu.reactive.rpc.model.RpcRequest;
import top.wangjiu.reactive.rpc.model.RpcResponse;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;

@AllArgsConstructor
public class RpcClientInvocationHandler<T> implements InvocationHandler {

    private Class<T> interfaceClass;

    private final ServiceConfig serviceConfig;

    private final ClientConfiguration clientConfiguration;

    private final Map<Method, AtomicLong> invokeCount = new ConcurrentHashMap<>();

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        String name = method.getName();
        if(name.equals("toString")){
            return interfaceClass.getName();
        }

        String requestId = buildRequestId(method,args);
        RpcRequest request = new RpcRequest(requestId, interfaceClass.getName(),method.getName(),serviceConfig.getVersion(),method.getParameterTypes(),args);

        List<InetSocketAddress> addressList = clientConfiguration.getServerAddrList().getAddressList(request.getService(), serviceConfig.getVersion());
        LoadBalance loadBalance = serviceConfig.getLoadBalance();
        RpcClient client = loadBalance.getClient(request,addressList,clientConfiguration.getContainer());

        Flux<RpcResponse> result = client.send(request);
        if(method.getReturnType().isAssignableFrom(Mono.class)){
            //这里的mapNotNull接受的Function可以返回空值,mapNotNull的意思是最终结果不会返回空值
            //而map方法，如果getResult 返回的是空值，就会抛出异常，意思是最终结果不允许为空，感觉这两个方法的名字会给人误解
            return result.single().mapNotNull(RpcResponse::getResult);
//            return result.single().flatMap(r->{
//                return Mono.justOrEmpty(r.getResult());
//            });
        }else if(method.getReturnType().isAssignableFrom(Flux.class)){
            return result.map(RpcResponse::getResult);
        }else if(method.getReturnType().isAssignableFrom(CompletableFuture.class)){
            return result.last().toFuture().thenApply(RpcResponse::getResult);
        }else{
            return result.blockFirst().getResult();
        }
    }

    private String buildRequestId(Method method, Object[] args){

        RequestIdGenerater generater = serviceConfig.getRequestIdGenerater();
        if(generater == null){
            generater = clientConfiguration.getRequestIdGenerater();
        }
        AtomicLong count = invokeCount.computeIfAbsent(method,method1 -> new AtomicLong(0l));

        return generater.get(interfaceClass,method,count.getAndIncrement(),args);
    }
}
